package neighborhood;

// Imported files
import road.Road;
import traveler.*;
import events.*;
import train.Train;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * The neighborhood class is the highest level element in the simulation. There
 * are four specific neighborhoods in this simulation. The Upper Class (UC), the
 * Lower Class (LC), Downtown (DT) and the Industrial (I). Each neighborhood has
 * a railroad connecting to two other neighborhoods and between 2 and 3 roads
 * connecting to the other neighborhoods.
 * <p>
 * Neighborhoods are represented as integers:
 * <p>
 * Upper Class (UC) = 0
 * <p>
 * Industrial (I) = 1
 * <p>
 * Downtown (DT) = 2
 * <p>
 * Lower Class (LC) = 3
 * 
 * @author Team Switchboard Boxcar
 * 
 */

public class Neighborhood
{
	private Road OutgoingRoad[];
	private Queue<Car>  NeighWaitCars_0;	// Cars Going CLKWISE
	private Queue<Car>  NeighWaitCars_1;	// Cars Going Diagonal
	private Queue<Car>  NeighWaitCars_2;	// Cars Going CCLKWISE
	private Queue<Car> RoadWaitCars_0;		// Cars Going CLKWISE
	private Queue<Car> RoadWaitCars_1;		// Cars Going Diagonal
	private Queue<Car> RoadWaitCars_2;		// Cars Going CCLKWISE
	private Queue<Traveler> TrainPass_0;			// Trains to Neigh #0
	private Queue<Traveler> TrainPass_1;			// Trains to Neigh #1
	private Queue<Traveler> TrainPass_2;			// Trains to Neigh #2
	private Queue<Traveler> TrainPass_3;			// Trains to Neigh #3
	private int neighID;
	
	
	/**
	 * Neighborhood is the basic constructor of the neighborhood class. When
	 * this is called 5 new {@link Road}s are created, 3 road waiting queues,
	 * and 3 neighborhood waiting queues are created.
	 * <p>
	 * If a neighborhood is not using one of the five roads, then the values
	 * input into this function to represent this road should be equal to 0.
	 * 
	 * @param MaxSpdArry
	 *            An array of the maximum speeds for all of the roads.
	 * @param LnSizeArry
	 *            An array of the initial lane sizes for all of the roads.
	 * @param RdLengthArry
	 *            An array of of the road lengths for each of the roads.
	 * @param LnMultArray
	 *            An array of the road lane multipliers for each of the roads.
	 * @param EndNeighArry
	 *            An array of integers representing the ending neighborhoods for
	 *            each of the roads. This value should be either 0, 1, 2, or 3.
	 */
	public Neighborhood(	int MaxSpdArry[], int LnSizeArry[],
							int RdLengthArry[], double LnMultArray[],
							int EndNeighArry[], int neighborhoodID)
	{
		OutgoingRoad = new Road[5];
		NeighWaitCars_0 = new ConcurrentLinkedQueue<Car>();
		NeighWaitCars_1 = new ConcurrentLinkedQueue<Car>();
		NeighWaitCars_2 = new ConcurrentLinkedQueue<Car>();
		RoadWaitCars_0 	= new ConcurrentLinkedQueue<Car>();
		RoadWaitCars_1 	= new ConcurrentLinkedQueue<Car>();
		RoadWaitCars_2 	= new ConcurrentLinkedQueue<Car>();
		TrainPass_0 	= new ConcurrentLinkedQueue<Traveler>();
		TrainPass_1 	= new ConcurrentLinkedQueue<Traveler>();
		TrainPass_2 	= new ConcurrentLinkedQueue<Traveler>();
		TrainPass_3 	= new ConcurrentLinkedQueue<Traveler>();
		neighID	= neighborhoodID;

		for (int i = 0; i < 5; i++) {
			if (	MaxSpdArry[i]	!= 0	&& 
					LnSizeArry[i]	!= 0	&& 
					RdLengthArry[i] != 0) 
			{	OutgoingRoad[i] =	new Road(MaxSpdArry[i], LnSizeArry[i],
											 RdLengthArry[i], LnMultArray[i], 
											 EndNeighArry[i]);
			}
			else
				OutgoingRoad[i] = new Road(0, 0, 0, 0, -1);
		}
	}
		
	/**
	 * roadExists is a function that can be called to determine whether or not a
	 * road exists at an input index. This is determined based upon whether or
	 * not the road's final destination = Neighborhood -1 and therfore does not
	 * exist.
	 * 
	 * @param roadIndx
	 *            An integer representing the index of the road.
	 * @return True means that a road exists while false means that a road does
	 *         not exist.
	 */
	public boolean roadExists(int roadIndx)
	{return -1 != OutgoingRoad[roadIndx].GetEndNeighborhood();}
	
	/**
	 * ReturnNeighQueue returns the queue that contains the {@link Car}s waiting to
	 * enter a {@link Road} having been generated in this neighborhood.
	 * 
	 * @param QueueNum
	 *            Integer values 0 - 4 representing the road number.
	 * @return The queue representing cars waiting to travel on the specified
	 *         road.
	 */
	public Queue ReturnNeighQueue(int QueueNum)
	{
		if(QueueNum == 0 ||
				QueueNum == 1)
			return  NeighWaitCars_0;
		if(QueueNum == 2)
			return  NeighWaitCars_1;
		if(QueueNum == 3 ||
				QueueNum == 4)
			return  NeighWaitCars_2;
		return null;
	}
	
	/**
	 * ReturnRoadQueue returns the queue that contains the {@link Car}s waiting to
	 * enter a {@link Road} having been generated in another neighborhood.
	 * 
	 * @param QueueNum
	 *            Integer values 0 - 4 representing the road number.
	 * @return The queue representing cars waiting to travel on the specified
	 *         road.
	 */
	public Queue ReturnRoadQueue(int QueueNum)
	{
		if(QueueNum == 0 ||
				QueueNum == 1)
			return RoadWaitCars_0;
		if(QueueNum == 2)
			return RoadWaitCars_1;
		if(QueueNum == 3 ||
			QueueNum == 4  )
			return RoadWaitCars_2;
		return null;
	}
	
	/**
	 * ReturnTrainQueue returns the queue that contains the {@link Traveler}s
	 * waiting to enter a {@link Train} having been generated in another neighborhood.
	 * 
	 * @param QueueNum
	 *            Integer values 0 - 4 representing the road number.
	 * @return The queue representing cars waiting to travel on the specified
	 *         road.
	 */
	public Queue ReturnTrainQueue(int QueueNum)
	{
		if(QueueNum == 0)
			return TrainPass_0;
		if(QueueNum == 1)
			return TrainPass_1;
		if(QueueNum == 2)
			return TrainPass_2;
		if(QueueNum == 3)
			return TrainPass_3;
		return null;
	}
	
	
/**
 * MoveCarToRoad is used to return a {@link Car} waiting in a queue for a
 * specific destination to the road. This function is passed the road that has
 * space to accomodate additional cars and then determines which queue has cars
 * that can be placed on this road.
 * 
 * @param RoadNumber
 *            Expects the road number (0 - 4) which has available road space to
 *            accomodate additional cars.
 * @return Returns a Car which had been placed in either the road or
 *         neighborhood queue for a specific road.
 */
	public Car MoveCarToRoad(int RoadNumber) 
	{  // Changed Return..Queue(#) to RoadNumber's
		if (RoadNumber == 0 || RoadNumber == 1) {
			if (!ReturnRoadQueue(RoadNumber).isEmpty())
				return (Car) (ReturnRoadQueue(RoadNumber).remove());
			else if (!ReturnNeighQueue(RoadNumber).isEmpty())
				return (Car) (ReturnNeighQueue(RoadNumber).remove());
		}
		if (RoadNumber == 2) {
			if (!ReturnRoadQueue(RoadNumber).isEmpty())
				return (Car) (ReturnRoadQueue(RoadNumber).remove());
			else if (!ReturnNeighQueue(RoadNumber).isEmpty())
				return (Car) (ReturnNeighQueue(RoadNumber).remove());
		}		
		if (RoadNumber == 3 || RoadNumber == 4) {
			if (!ReturnRoadQueue(RoadNumber).isEmpty())
				return (Car) (ReturnRoadQueue(RoadNumber).remove());
			else if (!ReturnNeighQueue(RoadNumber).isEmpty())
				return (Car) (ReturnNeighQueue(RoadNumber).remove());
		}
		return null;
	}


	/**
	 * TransferTrainPass is the function that transfers passengers at
	 * neighborhood. All passengers at their destination our unloaded and then
	 * the next set of passengers is loaded. The loaded passengers consist of
	 * 2/3 of passengers getting off at the first stop and 1/3 of the passengers
	 * getting off at the second stop.
	 * 
	 * @param IncomingTrain
	 *            This function takes in a train coming out of a TrainArrival
	 *            Event.
	 * @return	A List of {@link Traveler} is returned by this function.
	 */
	public List<Traveler> TransferTrainPass(Train IncomingTrain)	
	{
		// Unload Travelers
		List<Traveler> travelers;
		travelers = IncomingTrain.UnloadPassengers(neighID);

		// Load til train is full
		while (!IncomingTrain.isTrainFull()
				&& !(ReturnTrainQueue(
						(neighID + 1 * IncomingTrain.returnDirection() + 4) % 4)
						.isEmpty() && ReturnTrainQueue(
						(neighID + 2 * IncomingTrain.returnDirection() + 4) % 4)
						.isEmpty())) {
			if(!ReturnTrainQueue((neighID + 1*IncomingTrain.returnDirection() +4) % 4).isEmpty())
			IncomingTrain.AddPassenger(
					(Traveler)(ReturnTrainQueue((neighID + 1*IncomingTrain.returnDirection() +4) % 4).remove()));//,
					//(neighID + 1*IncomingTrain.returnDirection() +4) % 4);
			if (!IncomingTrain.isTrainFull() &&
					!ReturnTrainQueue((neighID + 1*IncomingTrain.returnDirection() +4) % 4).isEmpty()) {
				IncomingTrain.AddPassenger(
						(Traveler)(ReturnTrainQueue((neighID + 1*IncomingTrain.returnDirection() +4) % 4).remove()));//,
						//(neighID + 1*IncomingTrain.returnDirection() +4) % 4);
			}
			if (!IncomingTrain.isTrainFull() &&
					!ReturnTrainQueue((neighID + 2*IncomingTrain.returnDirection() +4) % 4).isEmpty()) {
				IncomingTrain.AddPassenger(
						(Traveler)(ReturnTrainQueue((neighID + 2*IncomingTrain.returnDirection() +4) % 4).remove()));//,
						//(neighID + 2*IncomingTrain.returnDirection() +4) % 4);
			}
		}
		
		// Return array of travelers removed from the train
		return travelers;		
	} 

	
	/**
	 * MoveCarToWait takes in a {@link Car} than moves it to a waiting queue
	 * dependent upon the Car's final destination. The final destination of a
	 * car can never be this specific neighborhood.
	 * 
	 * @param IncomingCar
	 *            A Car that arrives at this neighborhood but has not reached
	 *            its final destination.
	 */
	public void MoveCarToWait(Car IncomingCar)
	{		
			if(IncomingCar.GetDestination() 
					== OutgoingRoad[0].GetEndNeighborhood() ||
					IncomingCar.GetDestination() 
					== OutgoingRoad[1].GetEndNeighborhood()	)
				ReturnRoadQueue(0).add((Car)(IncomingCar));	
			if(IncomingCar.GetDestination() 
					== OutgoingRoad[2].GetEndNeighborhood())
				ReturnRoadQueue(1).add((Car)(IncomingCar));
			if(IncomingCar.GetDestination() 
					== OutgoingRoad[3].GetEndNeighborhood() ||
					IncomingCar.GetDestination() 
					== OutgoingRoad[4].GetEndNeighborhood() )
				ReturnRoadQueue(2).add((Car)(IncomingCar));		
		
	}
	
	/**
	 * UpdateRoadSpeed is a function that updates the speed of a specific road
	 * (0 - 4) in a Neighborhood.
	 * 
	 * @param roadNumber
	 *            Expects a the integer value representing the road that is
	 *            receiving an updated speed.
	 * @param newSpeed
	 *            Expects a double value representing the factor the speed is
	 *            being updated by.
	 */
	public void UpdateRoadSpeed(int roadNumber, double newSpeed)
	{
		OutgoingRoad[roadNumber].UpdateSpeed(newSpeed);				
	}
	
	
	/**
	 * GetCarsPerMin returns the the number of cars that can travel
	 * down a specific road in the neighborhood. This is used to build
	 * {@link Car_Arrival} events.
	 * 
	 * @param Road_Number
	 *            Expects an integer that represents a {@link Road} in the
	 *            simulation. 
	 * @return Returns the time it takes to travel down a road.
	 */
	public int GetCarsPerMin(int Road_Number)
	{
		return OutgoingRoad[Road_Number].GetCarsPerMin();
	}
	
	
	/**
	 * CreateTrain takes in the variables for a new {@link Train}, creates a
	 * Train and returns the created train to the simulation.
	 * 
	 * @param depart	Integer representing the departure time of the train.
	 * @param maxPass	Integer representing the maximum passengers on the train.
	 * @param direction	Value (1, -1) representing the direction of the train.
	 * @return	Returns the newly created train.
	 * @see Train
	 */
	public Train CreateTrain(int depart, int maxPass, int direction)
	{
		Train newTrain = new Train( depart,  maxPass,  direction);
		return newTrain;
	
	}
	
	/**
	 * ChangeDepartureTime updates the departure time of a train that just
	 * transfered its passengers at this neighborhood. This value is modfied by
	 * a value of 1 to represent the one minute it takes to load and unload
	 * passengers.
	 * 
	 * @param updateTrain
	 *            The Train that has just transfered its passengers.
	 * @param currentTime
	 *            The current time in the simulation.
	 * @return The time that the train departed the previous station.
	 */	
	public int ChangeDepartureTime(Train updateTrain, int currentTime)
	{
		int tempTime =	updateTrain.returnDepartTime();
		updateTrain.SetDepartTime(currentTime+ 1);
		return tempTime;
	}
	
	
	/**
	 * CreateTraveler creates a new {@link Traveler} which is then placed into the
	 * appropriate train waiting queue.
	 * 
	 * @param newDestination
	 *            The neighborhood index representing the destination of the
	 *            traveler being created.
	 * @param CurrentTime
	 *            The time value representing the current time of the system.
	 */
	public void CreateTraveler(int newDestination, int CurrentTime)
	{
		Traveler newTrav = new Traveler(newDestination, CurrentTime); 
		ReturnTrainQueue(newDestination).add(newTrav);
	}
	
	
	/**
	 * CreateCar creates a new {@link Car} which is then placed into the
	 * appropriate train waiting queue.
	 * 
	 * @param newDestination
	 *            The neighborhood index representing the destination of the
	 *            traveler being created.
	 * @param roadIndx
	 *            The road index the car needs to travel on to get to the next
	 *            neighborhood
	 * @param CurrentTime
	 *            The time value representing the current time of the system.
	 */
	public void CreateCar(int roadIndx, int newDestination, int CurrentTime)
	{
		Car newCar = new Car(newDestination, CurrentTime);
		ReturnNeighQueue(roadIndx).add(newCar);
	}
	
	
	/**
	 * Next is a static function which will get the next neighborhood index which is clockwise from the input
	 * 
	 * @param inputNeighborhood
	 *            A neighborhood index
	 * @return ClockwiseIndex
	 *            The next neighborhood index
	 */
	public static int Next(int inputNeighborhood)
	{
		inputNeighborhood++;
		return (inputNeighborhood <= 3)?inputNeighborhood:0;
	}
	
	
	/**
	 * Prev is a static function which will get the previous neighborhood index which is counter clockwise from the input
	 * 
	 * @param inputNeighborhood
	 *            A neighborhood index
	 * @return CounterClockwiseIndex
	 *            The previous neighborhood index
	 */
	public static int Prev(int inputNeighborhood)
	{
		inputNeighborhood--;
		return (inputNeighborhood >= 0)?inputNeighborhood:3;
	}
	/**
	 * GetNeighIndex returns the index of the current neighborhood.
	 * 
	 * @return Neighborhood ID
	 *            The index of the current neighborhood
	 */
	public int GetNeighIndex()
	{		return neighID;	}
	
	/**
	 * GetTravelTime returns the time that it takes to travel a single
	 * {@link Road} in the neigborhood.
	 * 
	 * @param roadIndx
	 *            The Index of the road the time is being calculated for.
	 * @return An integer representing the time that it takes to travel the
	 *         length of a road.
	 */
	public int GetTravelTime(int roadIndx)
	{	return OutgoingRoad[roadIndx].GetTravelTime();}
	
	/**
	 * GetRoadEndNeigh returns the end neighborhood of the specified
	 * {@link Road} in the neighborhood.
	 * 
	 * @param roadIndx
	 *            The Index of the road the end neighbhood is being returned
	 *            for.
	 * @return Returns an integer representing the end neighborhood of a
	 *         specified road.
	 */
	public int GetRoadEndNeigh(int roadIndx)
	{ return OutgoingRoad[roadIndx].GetEndNeighborhood();}
}

